#include "folder.h"

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>

#include "filehandle.h"
#include "path.h"

fs_ret_t fs_assertHandleIsFolder(FileSystemHandle_t *fsh,filehandleid_t handleId){
	FileHandle_t *handle;
	fs_ret_t ret = getFileHandle(fsh,handleId,&handle);
	if(ret) return ret;
	return handle->inode.isFolder?RET_FALSE:ERR_PARAMETER_PROBLEM;
}

fs_ret_t fs_assertInodeIsFolder(FileSystemHandle_t *fsh,inodeid_t inodeId){
	inode_t inode;
	fs_ret_t ret = fs_getInode(fsh,inodeId,&inode);
	if(ret) return ret;
	return inode.isFolder?RET_FALSE:ERR_PARAMETER_PROBLEM;
}

fs_ret_t fs_newFolder(FileSystemHandle_t *fsh, inodeid_t parentFolder, inodeid_t* address,bool noFather,bool noSelf){
	fs_ret_t ret = RET_NO_PROBLEM;
	ret = fs_newInode(fsh,address,true,true);
	if(ret) return ret;
	if(!noSelf){
		ret = fs_addInodeToFolder(fsh,*address,*address,".");
	}
	if(ret) return ret;
	if(!noFather){
		ret = fs_addInodeToFolder(fsh,*address,parentFolder,"..");
	}
	return ret;
}

fs_ret_t fs_deleteFolder(FileSystemHandle_t *fsh,inodeid_t folder){
	fs_ret_t ret = RET_NO_PROBLEM;
	filepos_t cursor = 0;
	inodeid_t currentInode;
	char *name = malloc(257);
	if(!name) return ERR_OUT_OF_MEMORY;
	while(!ret){
		ret = fs_getNextFolderItem(fsh,folder,&cursor,name,&currentInode);
		if(ret) break;
		if(strcmp(name,".")&&strcmp(name,"..")){
			ret = fs_removeInodeRef(fsh,currentInode);
			if(ret) break;
			DEBUG_PRINT("Removed %s(%d)\n",name,currentInode);
		}
	}
	if(ret==ERR_OUT_OF_RANGE) ret = RET_NO_PROBLEM;
	free(name);
	return ret;
}

fs_ret_t fs_addInodeToFolder(FileSystemHandle_t *fsh,inodeid_t folder,inodeid_t toAdd,const char* itemName){
	fs_ret_t ret = RET_NO_PROBLEM;
	filehandleid_t handleId;
	// Earning 2 char by not storing the string terminator
	// and storing size-1, assuming no empty named item.
	uint8_t stringSize = strlen(itemName)<256+1?(uint8_t)(strlen(itemName)-1):256-1;
	uint16_t dataSize = sizeof(inodeid_t)+sizeof(uint8_t)+stringSize+1;
	byte_t *data = malloc(sizeof(inodeid_t)+sizeof(uint8_t)+257);
	uint32_t itemsCount;
	filepos_t fileSize=0;
	if(!strlen(itemName)){
		return ERR_PARAMETER_PROBLEM;
	}
	ret = fs_newFileHandle(fsh,folder,&handleId);
	if(ret) goto cleanup;
	ret = fs_assertHandleIsFolder(fsh,handleId);
	if(ret)goto cleanup;
	{
		assert(!fs_getSizeFromFileHandle(fsh,handleId,&fileSize));
		if(fileSize!=0){
			fs_seek(fsh,handleId,FS_SEEK_SET,0);
			ret = fs_read(fsh,handleId,(byte_t*)&itemsCount,sizeof(uint32_t));
			if(ret) goto cleanup;
		}else{
			itemsCount = 0;
		}
		itemsCount+=1;
		fs_seek(fsh,handleId,FS_SEEK_SET,0);
		ret = fs_write(fsh,handleId,(byte_t*)&itemsCount,sizeof(uint32_t));
		if(ret) goto cleanup;
	}{
		ret = fs_seek(fsh,handleId,FS_SEEK_END,0);
		if(ret) goto cleanup;
		memcpy(data,&toAdd,sizeof(inodeid_t));
		memcpy(data+sizeof(inodeid_t),&stringSize,sizeof(uint8_t));
		memcpy(data+sizeof(inodeid_t)+sizeof(uint8_t),itemName,stringSize+1);
		ret = fs_write(fsh,handleId,data,dataSize);
		if(ret) goto cleanup;
	}
	ret = fs_deleteFileHandle(fsh,handleId);
	cleanup:
	free(data);
	return ret;
}

fs_ret_t fs_removeInodeFromFolder(FileSystemHandle_t *fsh,inodeid_t folder,inodeid_t toRemove){
	fs_ret_t ret = RET_NO_PROBLEM;
	inodeid_t newFolderInodeId;
	inode_t tempInode;
	filepos_t posInStartingFolder = 0;
	byte_t * block = NULL;
	char *currentName = malloc(257);
	inodeid_t currentInodeId;
	ret = fs_assertInodeIsFolder(fsh,folder);
	if(ret) return ret;
	ret = fs_newFolder(fsh,0,&newFolderInodeId,true,true);
	if(ret) return ret;
	DEBUG_PRINT("Filling the new folder\n");
	while(!ret){
		ret=fs_getNextFolderItem(fsh,folder,&posInStartingFolder,currentName,&currentInodeId);
		if(ret) break;
		if(currentInodeId != toRemove){
			ret = fs_addInodeToFolder(fsh,newFolderInodeId,currentInodeId,currentName);
			if(ret) return ret;
		}
	}
	if(ret==ERR_OUT_OF_RANGE){
			ret = RET_NO_PROBLEM;
	}else{
		return ret;
	}
	DEBUG_PRINT("Removing the previous blocks\n");
	ret = fs_getInode(fsh,folder,&tempInode);
	if(ret)  return ret;
	fs_deleteFileBlocks(fsh,&tempInode);
	DEBUG_PRINT("Replacing the inode\n");
	block = malloc(fsh->fsd.blockSize);
	if(!block){
		return ERR_OUT_OF_MEMORY;
	}
	ret = fs_getInode(fsh,newFolderInodeId,&tempInode);
	if(ret) goto cleanup;
	memcpy(block,&tempInode,sizeof(inode_t));
	ret = fs_setBlockToHdi(fsh->hdi,folder,block);
	DEBUG_PRINT("Removing the temporary inode(%d)\n",newFolderInodeId);
	fs_freeBlock(fsh,newFolderInodeId);
	if(ret) goto cleanup;
	cleanup:
	free(block);
	return ret;
}

fs_ret_t fs_createItemInFolder(FileSystemHandle_t *fsh,inodeid_t folder,inodeid_t* address,const char* name, bool isFolder){
	fs_ret_t ret;
	inodeid_t addressIfNone;
	if(!address) address = &addressIfNone;
	ret = fs_assertInodeIsFolder(fsh,folder);
	if(ret) return ret;
	if(isFolder){
		ret = fs_newFolder(fsh,folder,address, false,false);
	}else{
		ret = fs_newInode(fsh,address,false,true);
	}
	if(ret) return ret;
	ret = fs_addInodeToFolder(fsh,folder,*address,name);
	return ret;
}

fs_ret_t fs_getNextFolderItem(FileSystemHandle_t *fsh,inodeid_t folder,filepos_t *cursor,char* itemName,inodeid_t* itemAddress){
	fs_ret_t ret = RET_NO_PROBLEM;
	filehandleid_t handleId;
	uint8_t stringSize;
	bool fileHandleCreated = false;
	ret = fs_newFileHandle(fsh,folder,&handleId);
	if(ret) goto cleanup;
	ret = fs_assertHandleIsFolder(fsh,handleId);
	if(ret)goto cleanup;
	fileHandleCreated = true;
	//fs_getInode(fsh,folder,&inode);
	if(*cursor==0){
		*cursor = sizeof(uint32_t);
	}
	ret = fs_seek(fsh,handleId,FS_SEEK_SET,*cursor);
	if(ret) goto cleanup;
	ret = fs_read(fsh,handleId,(byte_t*)itemAddress,sizeof(inodeid_t));
	if(ret) goto cleanup;
	ret = fs_read(fsh,handleId,(byte_t*)&stringSize,sizeof(uint8_t));
	if(ret) goto cleanup;
	if(itemName){// Maybe the name is not needed.
		ret = fs_read(fsh,handleId,(byte_t*)itemName,stringSize+1);
		itemName[stringSize+1]='\0';
	}else{
		ret = fs_seek(fsh,handleId,FS_SEEK_CUR,stringSize+1);
	}
	if(ret) goto cleanup;
	fs_tell(fsh,handleId,cursor);
	cleanup:
	if(fileHandleCreated){
		if(!ret){
			ret = fs_deleteFileHandle(fsh,handleId);
		}else{
			fs_deleteFileHandle(fsh,handleId);
		}
	}
	return ret;
}

fs_ret_t fs_copyFile(FileSystemHandle_t *fsh,inodeid_t from,inodeid_t toFolder,char *name,inodeid_t *newInode){
	inodeid_t destInodeId;
	fs_ret_t ret;
	filehandleid_t handleFrom = 0, handleTo =0;
	bool handleFromInitialized = false, handleToInitialized = false;
	if(!fs_assertInodeIsFolder(fsh,from)){
		WARNING_PRINT("This function cannot copy folders.");
		return ERR_PARAMETER_PROBLEM;
	}
	// If exist, delete it.
	if(!fs_getItemInFolderbyName(fsh,toFolder,name,&destInodeId)){
		ret =  fs_removeInodeRef(fsh,destInodeId);
		if(ret) return ret;
	}
	ret = fs_createItemInFolder(fsh,toFolder,&destInodeId,name,false);
	if(ret){return ret;}
	ret = fs_newFileHandle(fsh,from,&handleFrom);
	if(ret) goto cleanup;
	handleFromInitialized = true;
	ret = fs_newFileHandle(fsh,destInodeId,&handleTo);
	if(ret) goto cleanup;
	if(newInode) *newInode = handleTo;
	handleToInitialized = true;
	/// \todo Optimize the code when read is functioning correctly.
	while(!ret){
		byte_t buf;
		ret = fs_read(fsh,handleFrom,&buf,1);
		if(ret){
			if(ret==ERR_OUT_OF_RANGE) ret = RET_NO_PROBLEM;
			break;
		}
		ret = fs_write(fsh,handleTo,&buf,1);
		if(ret) break;
	}
	cleanup:
	// No free of buf, it is just a char for now.
	if(handleFromInitialized) fs_deleteFileHandle(fsh,handleFrom);
	if(handleToInitialized) fs_deleteFileHandle(fsh,handleTo);
	return ret;
}

fs_ret_t fs_copyItemRecursive(FileSystemHandle_t *fsh,inodeid_t itemToCopy,inodeid_t toFolder,char *itemToCopyName,inodeid_t *newInode){
	fs_ret_t ret = RET_NO_PROBLEM;
	if(fs_assertInodeIsFolder(fsh,itemToCopy)){// File.
		ret = fs_copyFile(fsh,itemToCopy,toFolder,itemToCopyName,newInode);
		if(ret) goto cleanup;
	}else{// Folder.
		filepos_t cursor = 0;
		inodeid_t folderDestId;
		// If already exist an itemToCopy with this name, remove it.
		if(!fs_getItemInFolderbyName(fsh,toFolder,itemToCopyName,&folderDestId)){
			ret = fs_removeInodeFromFolder(fsh,toFolder,folderDestId);
			if(ret) goto cleanup;
			ret = fs_removeInodeRef(fsh,folderDestId);
			if(ret) goto cleanup;
		}
		// Copying self.
		ret = fs_createItemInFolder(fsh,toFolder,&folderDestId,itemToCopyName,true);
		if(ret){return ret;}
		while(!ret){// Copying contents.
			char currentItemToCopyName[257];
			inodeid_t currentItemToCopyId;
			ret = fs_getNextFolderItem(fsh,itemToCopy,&cursor,currentItemToCopyName,&currentItemToCopyId);
			if(ret) break;
			if(!strcmp("..",currentItemToCopyName) 
				|| !strcmp(".",currentItemToCopyName)) continue;
			ret = fs_copyItemRecursive(fsh,currentItemToCopyId,folderDestId,currentItemToCopyName,NULL);
			if(ret) break;
		}
		if(ret == ERR_OUT_OF_RANGE){
			ret = RET_NO_PROBLEM;
		}
		if(newInode) *newInode = folderDestId;
	}
	cleanup:
	return ret;
}
